#include "sorter.h"

#define MAX_FILE_PATH 1000

// Compare function for qsort
int compare(const void* a, const void* b)
{
  return (*(int*)a - *(int*)b);
}

Sorter::Sorter() {
  _logger = Logger::getInstance();
  source_file_mapper_ = new FileIO();
  dest_file_mapper_ = new FileIO();
}

Sorter::~Sorter() {
  delete source_file_mapper_;
  delete dest_file_mapper_;
}

bool Sorter::init(char* source_file, char* destination_file) {
  char* tag = "<INIT>";
  const int kLengthOfInfo = 59;
  int length = strlen(destination_file) + strlen(source_file) + kLengthOfInfo;
  char* info = (char*)malloc(length);
  sprintf(info, "Initialization started. Source file: %s, destination file: %s.", source_file, destination_file);
  _logger->info(info, tag);

  // Open handle for source file
  wchar_t* buffer = (wchar_t*)malloc(MAX_FILE_PATH);
  toWchar(source_file, buffer);
  if (!source_file_mapper_->loadFile(buffer, false)) {
    _logger->error("Failed to load source file", tag);
    free(buffer);
    return false;
  }
  // Get size of source file
  DWORD dw_file_size_low, dw_file_size_high;
  dw_file_size_low = source_file_mapper_->getFileSize(&dw_file_size_high);
  
  // Open handle for destination file with size of source file
  toWchar(destination_file, buffer);
  if (!dest_file_mapper_->loadFile(buffer, true, dw_file_size_high, dw_file_size_low)) {
    _logger->error("Failed to load destination file", tag);
    free(buffer);
    return false; 
  }

  free(buffer);
  _logger->info("Initialization finished", tag);
  return true;
}

bool Sorter::sort(char* source_file, char* destination_file) {
  if (!init(source_file, destination_file))
    return false;
  char* tag = "<SORT>";

  bool last_map = true;
  bool one_buffer = true;
  int n_of_buffers = 0;
  wchar_t** tmp_files = NULL;
  do {
    char* in_buffer = source_file_mapper_->getNextMap(&last_map);
    if (in_buffer == NULL) {
      _logger->error("Buffer is empty", tag);
      return false;
    }
    if (one_buffer && !last_map) {
      one_buffer = false;
    }
    char* out_buffer = NULL;
    FileIO* out_mapper = NULL;
    int size = last_map ? strlen(in_buffer) : SIZE_OF_BUFFER;
    if (one_buffer) {
      out_buffer = dest_file_mapper_->getNextMap();
    } else {
      wchar_t* tmp_file = new wchar_t[12];
      getTempName(++n_of_buffers, tmp_file);
      tmp_files = (wchar_t**)realloc(tmp_files, n_of_buffers * sizeof(wchar_t*));
      tmp_files[n_of_buffers - 1] = tmp_file;
      out_mapper = new FileIO(tmp_file, true, 0, size);
      out_buffer = out_mapper->getNextMap();
    }
    if (out_buffer == NULL) {
      _logger->error("Buffer is empty", tag);
      return false;
    }
    int* numbers = (int*)malloc(SIZE_OF_BUFFER / 2 * sizeof(int));
    int n_of_numbers = 0;
    DWORD last_space = -1;
    int number = 0;
    // Read chars from buffer and input to int array
    for (int i = 0; i < size; i++) {
      char symbol = in_buffer[i];
      if (symbol >= '0' && symbol <= '9') {
        number *= 10;
        number += symbol - '0';
        if (last_map && i == size - 1) {
          addNumber(numbers, &number, &n_of_numbers);
        }
      } else if (symbol == ' ') {
        if (last_space != i - 1) {
          addNumber(numbers, &number, &n_of_numbers);
        }
        last_space = i;
      } else {
        _logger->warn("Bad symbol. Exiting.", tag);
        UnmapViewOfFile(in_buffer);
        UnmapViewOfFile(out_buffer);
        return false;
      }
    }
    // sorting
    qsort(numbers, n_of_numbers, sizeof(unsigned int), compare);

    // Output integers to file
    int output_position = 0;
    for (int i = 0; i < n_of_numbers; i++) {
      int number = numbers[i];
      if (number == 0) {
        out_buffer[output_position++] = '0';
      } else {
        int n_of_digits = 0;
        char* tmp = NULL;
        while (number > 0) {
          tmp = (char*) realloc(tmp, (++n_of_digits) * sizeof(char));
          tmp[n_of_digits - 1] = number % 10 + '0';
          number /= 10;
        }
        for (int j = n_of_digits - 1; j >= 0; j--) {
          out_buffer[output_position++] = tmp[j];
        }
        delete[] tmp;
      }
      out_buffer[output_position++] = ' ';
    } 
    UnmapViewOfFile(in_buffer);
    UnmapViewOfFile(out_buffer);
    if (out_mapper) {
      delete out_mapper;
    }
    delete[] numbers;
  } while (!last_map);

  if (!one_buffer) {
    merge(n_of_buffers);
    // Deaalocate memory for temp files and delete them
    for (int i = 0; i < n_of_buffers; i++) {
      DeleteFile(tmp_files[i]);
      delete[] tmp_files[i];
    }
    delete[] tmp_files;
  }
  return true;

} 

void Sorter::toWchar(char* str, wchar_t* result) {
  int required_size = mbstowcs(NULL, str, 1);
  mbstowcs(result, str, required_size);
  const int kMaxExtraBits = 7;
  memset(result + required_size, 0, kMaxExtraBits);
}

void Sorter::merge(int n_of_buffers) {
  // TODO: merge sorted files
}

void Sorter::getTempName(int n, wchar_t* tmp_name) {
  if (n < 1 || n > 9999)
    return;
  wsprintf(tmp_name, L"tmp0000.txt");
  int position = 6;
  while (n > 0) {
    int digit = n % 10;
    // Digit to wchar_t conversion
    wchar_t* dig = new wchar_t[2];
    _itow_s(digit, dig, 2, 10);

    tmp_name[position--] = dig[0];
    n /= 10;
    delete[] dig;
  }
}

void Sorter::addNumber(int* numbers, int* number, int* n_of_numbers) {
  //numbers = (int*)realloc(numbers, (++(*n_of_numbers)) * sizeof(int));
  numbers[(*n_of_numbers)++] = *number;
  *number = 0;
}